package dp.mobile.glwrapper;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.CharBuffer;
import java.nio.FloatBuffer;
import java.util.ArrayList;

import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;

import android.util.Log;

import dp.mobile.entity.Node;
import dp.mobile.game.Tileset;
import dp.mobile.system.Utilities;

public class MapRenderer {
	private static final String TAG = "Map Renderer";
	public MapRenderer(Tileset tileset, int[][] tiles, ArrayList<Node> nodes) {
		mTileset		= tileset;
		mTiles			= tiles;
		mNodes			= nodes;
		mVBOPrepared 	= false;
		mMainTexture	= tileset.getTile(tiles[0][0]).Texture;

		mQuadH = tiles[0].length;
		mQuadW = tiles.length;
		final int quadCount	= mQuadW * mQuadH;
		
		mVertexPointer	= new int[quadCount];
		mTexturePointer	= new int[quadCount];
		mVertexBuffer 	= new FloatBuffer[quadCount];
		mTextureBuffer 	= new FloatBuffer[quadCount];
		
		final float depth 	= Utilities.MAP_DEPTH;
		
		for (int y = 0; y < mQuadH; ++y) {
			for (int x = 0; x < mQuadW; ++x) {
				int i = y * mQuadW + x;
				
				mVertexBuffer[i] = ByteBuffer.allocateDirect(4 * 4 * 3)
		    	.order(ByteOrder.nativeOrder()).asFloatBuffer();
				mTextureBuffer[i] = ByteBuffer.allocateDirect(4 * 4 * 2)
		    	.order(ByteOrder.nativeOrder()).asFloatBuffer();
				
				final float left	= x * Utilities.TILE_WIDTH * GameRenderer.instance().getScale().x;
				final float right	= (x + 1) * Utilities.TILE_WIDTH  * GameRenderer.instance().getScale().x;
				final float top		= - ((y + 1) * Utilities.TILE_HEIGHT)  * GameRenderer.instance().getScale().y;
				final float bottom	= - (y * Utilities.TILE_HEIGHT)  * GameRenderer.instance().getScale().y;
				
				final float[] vertex = {
						left,	top, depth,
						right, 	top, depth,
						left,	bottom, depth,
						right, 	bottom, depth,
					};
				
				mVertexBuffer[i].put(vertex);
				mTextureBuffer[i].put(mTileset.getTile(mTiles[x][y]).Crop);
				
				mVertexBuffer[i].position(0);
				mTextureBuffer[i].position(0);
			}
		}
		

		mIndexBuffer = ByteBuffer.allocateDirect(2 * 6)
		.order(ByteOrder.nativeOrder()).asCharBuffer();
		mIndexBuffer.put(QUAD_INDICES);
		mIndexBuffer.position(0);
		
		mNodeTextureBuffer = ByteBuffer.allocateDirect(4 * 4 * 2)
    	.order(ByteOrder.nativeOrder()).asFloatBuffer();
		mNodeTextureBuffer.put(GameRenderer.FLIPPED_TEXTURE);
		mNodeTextureBuffer.position(0);
	}
	
	private void prepareVBO(GL11 gl) {
		int[] buffer = new int[1];
		
		for (int y = 0; y < mQuadH; ++y) {
			for (int x = 0; x < mQuadW; ++x) {
				int i = y * mQuadW + x;
				
				gl.glGenBuffers(1, buffer, 0);
				mVertexPointer[i] = buffer[0];
				gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, mVertexPointer[i]);
		        gl.glBufferData(GL11.GL_ARRAY_BUFFER, 48, mVertexBuffer[i], GL11.GL_STATIC_DRAW);
		        
		        gl.glGenBuffers(1, buffer, 0);
				mTexturePointer[i] = buffer[0];
				gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, mTexturePointer[i]);
		        gl.glBufferData(GL11.GL_ARRAY_BUFFER, 32, mTextureBuffer[i], GL11.GL_STATIC_DRAW);
		        
		        
			}
		}
		
		gl.glGenBuffers(1, buffer, 0);
		mIndexPointer = buffer[0];
		gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, mIndexPointer);
        gl.glBufferData(GL11.GL_ELEMENT_ARRAY_BUFFER, 12, mIndexBuffer, GL11.GL_STATIC_DRAW);
        
        gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, 0);
		
		gl.glGenBuffers(1, buffer, 0);
		mNodeTexturePointer = buffer[0];
		gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, mNodeTexturePointer);
        gl.glBufferData(GL11.GL_ARRAY_BUFFER, 32, mNodeTextureBuffer, GL11.GL_STATIC_DRAW);
		
		mVBOPrepared = true;
	}
	
	public void draw(GL11 gl, int startX, int startY, int endX, int endY) {
		if (!mVBOPrepared)
			prepareVBO(gl);
		
		GameRenderer renderer = GameRenderer.instance();
		
		// prepare draw
		gl.glMatrixMode(GL10.GL_MODELVIEW);
		gl.glPushMatrix();
		gl.glLoadIdentity();
		gl.glTranslatef(renderer.getOffset().x * renderer.getScale().x, renderer.getOffset().y * renderer.getScale().y, renderer.getDepth());

		// draw map
		gl.glBindTexture(GL10.GL_TEXTURE_2D, mMainTexture.Name);
        gl.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
        gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, mIndexPointer);
        for (int y = startY; y < endY; ++y) {
        	for (int x = startX; x < endX; ++x) {
        		int i = y * mQuadW + x;
        		
        		final float left 	= (x * Utilities.TILE_WIDTH + renderer.getOffset().x) * renderer.getScale().x;
        		final float top		= (-((y + 1) * Utilities.TILE_HEIGHT) + renderer.getOffset().y) * renderer.getScale().y; 
        		
        		if (renderer.cullTexture(left, top, Utilities.TILE_WIDTH * renderer.getScale().x, Utilities.TILE_HEIGHT * renderer.getScale().y)) continue;

        		// draw tileset
				gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, mVertexPointer[i]);
		        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, 0);
		        gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, mTexturePointer[i]);
		        gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, 0);
		        
		        //gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, mIndexPointer);
		        gl.glDrawElements(GL11.GL_TRIANGLES, 6, GL11.GL_UNSIGNED_SHORT, 0);
        	}
        }
        
        // draw node
        gl.glTranslatef(0, 0, Utilities.NODE_DEPTH);
        gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, mNodeTexturePointer);
        gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, 0);
        for (int p = 0; p < mNodes.size(); ++p) {
        	Node curNode = mNodes.get(p);
        	final int x = curNode.getTileX();
        	final int y = curNode.getTileY();
        	int i = y * mQuadW + x;
    		
    		final float left 	= (x * Utilities.TILE_WIDTH + renderer.getOffset().x) * renderer.getScale().x;
    		final float top		= (-((y + 1) * Utilities.TILE_HEIGHT) + renderer.getOffset().y) * renderer.getScale().y; 
    		
    		if (renderer.cullTexture(left, top, Utilities.TILE_WIDTH * renderer.getScale().x, Utilities.TILE_HEIGHT * renderer.getScale().y)) continue;

    		gl.glBindTexture(GL10.GL_TEXTURE_2D, curNode.getImage().Name);
    		
	        // draw nodes
    		gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, mVertexPointer[i]);
	        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, 0);
	        
	        gl.glDrawElements(GL11.GL_TRIANGLES, 6, GL11.GL_UNSIGNED_SHORT, 0);
        }
        
		// cleaning up
		//gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, 0);
		gl.glPopMatrix();
	}
	
	public void invalidateVBO() {
		mVBOPrepared = false;
	}
	
	private boolean mVBOPrepared;
	private int mQuadW, mQuadH;
	private Tileset mTileset;
	private GLTexture mMainTexture;
	private int[][] mTiles;
	private ArrayList<Node> mNodes;
	private int[] mVertexPointer, mTexturePointer;
	private int mNodeTexturePointer, mIndexPointer;
	private FloatBuffer[] mVertexBuffer, mTextureBuffer;
	private FloatBuffer mNodeTextureBuffer;
	private CharBuffer mIndexBuffer;
	
	private static final char[] QUAD_INDICES = {0, 1, 2, 1, 2, 3};
}
